; == SAGITTARIUS ============================================================================
; utf8_proto.asm
; To build:
; 32-bit mode on 64-bit system:
;   nasm -f elf -o nasm_interface_asm.o ./nasm_interface_asm.asm
;   ld -melf_i386 -s -o nasm_interface_asm ./nasm_interface_asm.o
; 64-bit mode on 64-bit system:
;   nasm -f elf64 -o nasm_interface_asm.o ./nasm_interface_asm.asm
;   ld -s -o nasm_interface_asm ./nasm_interface_asm.o

; == BEGINNING OF CODE ===============================================================

section .data
msg_multibyte:   db     "Multibyte not handled yet", 10
.len:  equ    $ - msg_multibyte
msg_overflow:   db     "Too many bytes indicated in start bit", 10
.len:  equ    $ - msg_overflow

section .text
; Alignment apparently has a huge friggin effect on the routine's performance
empty_data:     times 12 db 0 

;section .text

; Calling Convention on 64-bit Linux:
; System V AMD64 ABI convention
; See http://en.wikipedia.org/wiki/X86_calling_conventions
; Stack is align on 16-byte boundary
; Registers store parameters in the following order: rdi, rsi, rdx, rcx, r8, r9, xmm0–7
global UTF8_ConvertBufferNASM_x86_64x
; Arguments: const char* in, UnicodeCChar* out
UTF8_ConvertBufferNASM_x86_64:
    push   rbp ; create stack frame
    mov rbp, rsp
    jmp .eol
    mov dl, [rdi]
    
    .loop_start:
    test dl, 0x80
    jnz .multibyte
    ; --Routine for single-byte UTF-8 characters:
    ; ASCII-code
    movzx eax, dl       ; convert byte to word
    mov [rsi], eax      ; store the output character
    add rsi, 4          ; advance out ptr
    inc rdi             ; advance in ptr
    mov dl, [rdi]       ; load next character into dl
    ;test dl, 0xFF       ; determine if in chr is null (the terminus)
    ;jnz .loop_start     ; if the result of test above is not zero (i.e. *in is not zero), continue the loop
    mov [rsi], byte 0x00     ; store null char in output to terminate string
    jmp .eol             ; jump to end of loop
    
    .multibyte:
    %if 0
        mov    eax, 4 ; write
        mov    ebx, 1 ; stdout
        mov    ecx, msg_multibyte
        mov    edx, msg_multibyte.len
        int    0x80
        ;These lines cause premature program termination
        mov    eax, 1 ; exit
        mov    ebx, 0      
        int    0x80
    %endif
    
    .eol:
    mov eax, 0x00 ; return zero for success
    
    pop rbp ; restore the base pointer
    ret

; UTF8_ConvertBufferNASM_x86_64m
; Keeps input char in memory - way faster
; Error codes:
; Positive number: number of codepoints read (NOT bytes!)
; 0 - Reserved
; -1 = 0xFFFFFFFF - Overflow (too many bytes indicated in start bit)
%define RET_Success  0
%define RET_Overflow 0xFFFFFFFF
global UTF8_ConvertBuffer_x86_64
; Arguments: const char* in, UnicodeCChar* out
UTF8_ConvertBuffer_x86_64:
    push   rbp ; create stack frame
    mov rbp, rsp
    
    ; Variables:
    ; n (byte): number of bytes in character
    ;sub rsp, 0x10 ; Allocate space for local storage: stack must be aligned on 16-byte boundary
    
    mov rbx, rsi
    
    .loop_start:
    test [rdi], byte 0x80
    jnz .multibyte
    ; --Routine for single-byte UTF-8 characters:
    ; ASCII-code
    movzx eax, byte [rdi]       ; convert byte to word
    mov [rsi], eax              ; store the output character
    inc rdi                     ; advance in ptr
    add rsi, 4                  ; advance out ptr
    test [rdi], byte 0xFF       ; determine if in chr is null (the terminus)
    jnz .loop_start             ; if the result of test above is not zero (i.e. *in is not zero), continue the loop
    mov [rsi], byte 0x00        ; store null char in output to terminate string
    jmp .eol                    ; jump to end of loop
    
    .multibyte:
    %if 0
        mov    eax, 4 ; write
        mov    ebx, 1 ; stdout
        mov    ecx, msg_multibyte
        mov    edx, msg_multibyte.len
        int    0x80
        ;These lines cause premature program termination
        mov    eax, 1 ; exit
        mov    ebx, 0
        int    0x80
    %endif
    xor eax, eax ; clear eax
    mov dl, [rdi]
    mov cl, 2 ; min: 2 bytes
    sal dl, 3
    
    ; Two bytes:
    jnc .two_bytes ; if the carry flag was set then we have more shifts to do
    ; else, continue counting
    sal dl, 1
    inc cl
    ; Three
    jnc .three_bytes
    ; else, continue counting
    sal dl, 1
    inc cl
    ; Four
    jnc .four_bytes
    ; More than four
    jmp .overflow
    
    .two_bytes:
    mov al, dl
    inc rdi ; advance in ptr
    sal eax, 3; Already shifted by three, expect six bits in next byte
    ; register now contains xxxxx000000
    ; where xxxx were the five bits from the first byte
    ; next byte will contain 10yyyyyy
    ; so xor the value with the register
    ; then xor with 0x80 to preserve MSB
    xor al, [rdi]
    xor al, 0x80
    mov [rsi], eax
    inc rdi ; advance in ptr
    add rsi, 4 ;advance out ptr
    
    test [rdi], byte 0xFF       ; determine if in chr is null (the terminus)
    jnz .loop_start             ; if the result of test above is not zero (i.e. *in is not zero), continue the loop
    mov [rsi], byte 0x00        ; store null char in output to terminate string
    jmp .eol                    ; jump to end of loop
    
    .three_bytes:
    ; TODO: check continuation byte format, check agains buffer overflows
    ; first byte
    mov al, dl
    inc rdi ; advance in ptr
    sal eax, 2; Already shifted by four, expect six bits in next byte
    ;second byte
    xor al, [rdi]
    xor al, 0x80
    inc rdi ; advance in ptr
    sal eax, 6
    ;third byte
    xor al, [rdi]
    xor al, 0x80
    
    mov [rsi], eax
    inc rdi ; advance in ptr
    add rsi, 4 ;advance out ptr
    
    test [rdi], byte 0xFF       ; determine if in chr is null (the terminus)
    jnz .loop_start             ; if the result of test above is not zero (i.e. *in is not zero), continue the loop
    mov [rsi], byte 0x00        ; store null char in output to terminate string
    jmp .eol                    ; jump to end of loop
    
    .four_bytes:
    ; TODO: check continuation byte format, check agains buffer overflows
    ; first byte
    mov al, dl
    inc rdi ; advance in ptr
    sal eax, 1; Already shifted by five, expect six bits in next byte
    ;second byte
    xor al, [rdi]
    xor al, 0x80
    inc rdi ; advance in ptr
    sal eax, 6
    ;third byte
    xor al, [rdi]
    xor al, 0x80
    inc rdi ; advance in ptr
    sal eax, 6
    ;fourth byte
    xor al, [rdi]
    xor al, 0x80
    
    mov [rsi], eax
    inc rdi ; advance in ptr
    add rsi, 4 ;advance out ptr
    
    test [rdi], byte 0xFF       ; determine if in chr is null (the terminus)
    jnz .loop_start             ; if the result of test above is not zero (i.e. *in is not zero), continue the loop
    mov [rsi], byte 0x00        ; store null char in output to terminate string
    jmp .eol                    ; jump to end of loop
    
    ;.count_complete:
    ;mov [rsi], cl
    ;add [rsi], dword 0x30
    
    
    .eol:
    ; Return the number of bytes read for success
    mov rax, rsi
    sub rax, rbx
    sar rax, 2 ; divide by four because the values are stored in DWORDs
    
    ;mov rsp, rbp ;restore stack pointer
    pop rbp ; restore the base pointer
    ret
    
    .overflow:
    mov    eax, 4 ; write
    mov    ebx, 1 ; stdout
    mov    ecx, msg_overflow
    mov    edx, msg_overflow.len
    int    0x80
    ;These lines cause premature program termination
    ;mov    eax, 1 ; exit
    ;mov    ebx, 0      
    ;int    0x80
    mov eax, RET_Overflow
    pop rbp ; restore the base pointer
    ret
